import time

'''
    雪花算法实现全局唯一ID生成器，适用 serverless 函数计算场景。
    - 全局唯一性：不能出现重复的 ID 号，既然是唯一标识，这是最基本的要求；
    - 趋势递增：在MySQL InnoDB引擎中使用的是聚集索引，由于多数RDBMS使用B-tree的数据结构来存储索引数据，
        在主键的选择上面我们应该尽量使用有序的主键保证写入性能；
    - 单调递增：保证下一个 ID 一定大于上一个 ID，例如事务版本号、IM 增量消息、排序等特殊需求；
    - 信息安全：如果 ID 是连续的，恶意用户的爬取工作就非常容易做了，直接按照顺序下载指定 URL 即可；
        如果是订单号就更危险了，竞争对手可以直接知道我们一天的单量。所以在一些应用场景下，会需要 ID 无规则、不规则。

    snowflake 不借助数据库, 可直接由编程语言生成, 它通过巧妙的位设计使得 ID 能够满足递增属性, 且生成的 ID 并不是依次连续的.

    snowflake 实现原理：snowflake 以 64bit 来存储组成 ID 的四个部分：
    64bit：1bit(标志位) | 41bit(时间戳) | 10bit(工作机器ID) | 12bit(1ms 2^12)
    
    1 bit：标志位，值固定为 0, 以保证生成的 ID 为正数
    41 bit：表示的是时间戳，单位是毫秒，可以表示 2^41-1 个毫秒值, 即69年的时间
    10 bit：记录机器id，代表的是这个服务最多可以部署在 2^10 = 1024 台机器上
    12 bit：用来记录同一个毫秒内产生的不同id，值的上限为 2^12 = 4096，
            也就是说可以用这个12 bit代表的数字来区分同一个毫秒内的4096个不同的id
            也就是同一毫秒内同一台机器所生成的最大ID数量为4096

    简单来说：
    1. 你的某个服务假设要生成一个全局唯一id，就可以发送一个请求给部署了snowFlake算法的系统，由这个系统生成唯一id
    2. 这个snowFlake算法系统首先肯定是知道自己所在的机器号，（这里姑且讲10bit全部作为工作机器ID）
    3. 这个snowFlake算法系统接收到这个请求之后，首先就会用二进制位运算的方式生成一个64 bit的long型id
    4. 64个bit中的第一个bit是无意义的
    5. 接着用当前时间戳（单位到毫秒）占用41个bit
    6. 然后接着10个bit设置机器id
    7. 最后再判断一下，当前这台机房的这台机器上这一毫秒内，这是第几个请求，给这次生成id的请求累加一个序号，作为最后的12个bit 

    https://blog.csdn.net/qq_40566294/article/details/107407626
    https://blog.csdn.net/feifeiyechuan/article/details/108236261
    推荐：https://www.cnblogs.com/oklizz/p/11865750.html
'''

class InvalidSystemClock(Exception):
    """
    时钟回拨异常
    """
    pass

# 机器ID占位5 这也就意味者十进制下编号不能超过31  将机器ID与机房ID合并，最大三个机房即00 10 20 每个机房的数值 + 1 即是机器ID  备用 30 31
# 64位ID的划分
WORKER_ID_BITS = 5
DATACENTER_ID_BITS = 5
SEQUENCE_BITS = 12

# 最大取值计算
MAX_WORKER_ID = -1 ^ (-1 << WORKER_ID_BITS)  # 2^5-1   31
MAX_DATACENTER_ID = -1 ^ (-1 << DATACENTER_ID_BITS) # 31

# 移位偏移计算
WOKER_ID_SHIFT = SEQUENCE_BITS
DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS
TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS

# 序号循环掩码
SEQUENCE_MASK = -1 ^ (-1 << SEQUENCE_BITS)

# 开始时间截 (2015-01-01)
TWEPOCH = 1420041600000


class IdWorker(object):
    def __init__(self, datacenter_id, worker_id, sequence=0):
        """
        初始化
        :param datacenter_id: 数据中心（机器区域）ID
        :param worker_id: 机器ID
        :param sequence: 起始序号
        """
        # sanity check
        if worker_id > MAX_WORKER_ID or worker_id < 0:
            raise ValueError('worker_id值越界')

        if datacenter_id > MAX_DATACENTER_ID or datacenter_id < 0:
            raise ValueError('datacenter_id值越界')

        self.worker_id = worker_id
        self.datacenter_id = datacenter_id
        self.sequence = sequence

        self.last_timestamp = -1  # 上次计算的时间戳

    def _gen_timestamp(self):
        """
        生成整数时间戳
        :return:int timestamp
        """
        return int(time.time() * 1000)

    def get_id(self):
        """
        获取新ID
        :return:
        """
        timestamp = self._gen_timestamp()
        # 时钟回拨
        if timestamp < self.last_timestamp:
            raise InvalidSystemClock

        if timestamp == self.last_timestamp:
            self.sequence = (self.sequence + 1) & SEQUENCE_MASK
            if self.sequence == 0:
                timestamp = self._til_next_millis(self.last_timestamp)
        else:
            self.sequence = 0

        self.last_timestamp = timestamp

        new_id = ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | (self.datacenter_id << DATACENTER_ID_SHIFT) | \
                 (self.worker_id << WOKER_ID_SHIFT) | self.sequence
        return new_id

    def _til_next_millis(self, last_timestamp):
        """
        等到下一毫秒
        """
        timestamp = self._gen_timestamp()
        while timestamp <= last_timestamp:
            timestamp = self._gen_timestamp()
        return timestamp


if __name__ == '__main__':
    worker = IdWorker(0, 0)
    print(worker.get_id())